package com.google.code.swingonfire.component.page;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;

import com.google.code.swingonfire.utility.interpolation.Interpolation;

public class JPageComponentTransitionAnimation implements JPageComponentAnimation
{

    public static interface Transition
    {

        void apply(Graphics2D g, int width, int height, double fraction, boolean forward);

    }

    public abstract static class AbstractTransition implements Transition
    {
        private final Interpolation interpolation;

        public AbstractTransition(Interpolation interpolation)
        {
            super();

            this.interpolation = interpolation;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void apply(Graphics2D g, int width, int height, double fraction, boolean forward)
        {
            if (interpolation != null)
            {
                fraction = interpolation.calculate(fraction);
            }

            applyInterpolated(g, width, height, fraction, forward);
        }

        protected abstract void applyInterpolated(Graphics2D g, int width, int height, double fraction, boolean forward);
    }

    public abstract static class AbstractSingleTransition extends AbstractTransition
    {
        private final double start;
        private final double end;

        public AbstractSingleTransition(Interpolation interpolation, double start, double end)
        {
            super(interpolation);

            this.start = start;
            this.end = end;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected final void applyInterpolated(Graphics2D g, int width, int height, double fraction, boolean forward)
        {
            applyInterpolated(g, width, height, fraction, forward, start, end);
        }

        protected abstract void applyInterpolated(Graphics2D g, int width, int height, double fraction,
            boolean forward, double start, double end);
    }

    public abstract static class AbstractDoubleTransition extends AbstractTransition
    {
        private final double startX;
        private final double endX;
        private final double startY;
        private final double endY;

        public AbstractDoubleTransition(Interpolation interpolation, double startX, double endX, double startY,
            double endY)
        {
            super(interpolation);

            this.startX = startX;
            this.endX = endX;
            this.startY = startY;
            this.endY = endY;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected final void applyInterpolated(Graphics2D g, int width, int height, double fraction, boolean forward)
        {
            applyInterpolated(g, width, height, fraction, forward, startX, endX, startY, endY);
        }

        protected abstract void applyInterpolated(Graphics2D g, int width, int height, double fraction,
            boolean forward, double startX, double endX, double startY, double endY);
    }

    public static class Opacity extends AbstractSingleTransition
    {
        public Opacity(Interpolation interpolation, double start, double end)
        {
            super(interpolation, start, end);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void applyInterpolated(Graphics2D g, int width, int height, double fraction, boolean forward,
            double start, double end)
        {
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                (float) (start + ((end - start) * fraction))));
        }
    }

    public static class Translate extends AbstractDoubleTransition
    {
        public Translate(Interpolation interpolation, double startX, double endX, double startY, double endY)
        {
            super(interpolation, startX, endX, startY, endY);
        }

        @Override
        protected void applyInterpolated(Graphics2D g, int width, int height, double fraction, boolean forward,
            double startX, double endX, double startY, double endY)
        {
            g.translate((startX + ((endX - startX) * fraction)) * width, (startY + ((endY - startY) * fraction))
                * height);
        }

    }

    public static class Scale extends AbstractDoubleTransition
    {
        public Scale(Interpolation interpolation, double startX, double endX, double startY, double endY)
        {
            super(interpolation, startX, endX, startY, endY);
        }

        @Override
        protected void applyInterpolated(Graphics2D g, int width, int height, double fraction, boolean forward,
            double startX, double endX, double startY, double endY)
        {
            g.scale(startX + ((endX - startX) * fraction), startY + ((endY - startY) * fraction));
        }
    }

    public static class Rotate extends AbstractSingleTransition
    {
        private final double centerX;
        private final double centerY;

        public Rotate(Interpolation interpolation, double centerX, double centerY, double start, double end)
        {
            super(interpolation, start, end);

            this.centerX = centerX;
            this.centerY = centerY;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void applyInterpolated(Graphics2D g, int width, int height, double fraction, boolean forward,
            double start, double end)
        {
            int distance = Math.max(width, height);
            
            g.translate(centerX * distance, centerY * distance);
            g.rotate(start + ((end - start) * fraction));
            g.translate(-centerX * distance, -centerY * distance);
        }

    }

    private final List<Transition> forwardIncomingTransitions = new ArrayList<Transition>();
    private final List<Transition> forwardOutgoingTransitions = new ArrayList<Transition>();
    private final List<Transition> backwardIncomingTransitions = new ArrayList<Transition>();
    private final List<Transition> backwardOutgoingTransitions = new ArrayList<Transition>();

    public JPageComponentTransitionAnimation()
    {
        super();
    }

    public JPageComponentTransitionAnimation addForwardIncoming(Transition transition)
    {
        forwardIncomingTransitions.add(transition);

        return this;
    }

    public JPageComponentTransitionAnimation addForwardOutgoing(Transition transition)
    {
        forwardOutgoingTransitions.add(transition);

        return this;
    }

    public JPageComponentTransitionAnimation addBackwardIncoming(Transition transition)
    {
        backwardIncomingTransitions.add(transition);

        return this;
    }

    public JPageComponentTransitionAnimation addBackwardOutgoing(Transition transition)
    {
        backwardOutgoingTransitions.add(transition);

        return this;
    }

    public JPageComponentTransitionAnimation forwardIncomingOpacity(Interpolation interpolation, double start, double end)
    {
        return addForwardIncoming(new Opacity(interpolation, start, end));
    }

    public JPageComponentTransitionAnimation forwardOutgoingOpacity(Interpolation interpolation, double start, double end)
    {
        return addForwardOutgoing(new Opacity(interpolation, start, end));
    }

    public JPageComponentTransitionAnimation backwardIncomingOpacity(Interpolation interpolation, double start, double end)
    {
        return addBackwardIncoming(new Opacity(interpolation, start, end));
    }

    public JPageComponentTransitionAnimation backwardOutgoingOpacity(Interpolation interpolation, double start, double end)
    {
        return addBackwardOutgoing(new Opacity(interpolation, start, end));
    }

    public JPageComponentTransitionAnimation forwardIncomingTranslate(Interpolation interpolation, double startX,
        double endX, double startY, double endY)
    {
        return addForwardIncoming(new Translate(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation forwardOutgoingTranslate(Interpolation interpolation, double startX,
        double endX, double startY, double endY)
    {
        return addForwardOutgoing(new Translate(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation backwardIncomingTranslate(Interpolation interpolation, double startX,
        double endX, double startY, double endY)
    {
        return addBackwardIncoming(new Translate(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation backwardOutgoingTranslate(Interpolation interpolation, double startX,
        double endX, double startY, double endY)
    {
        return addBackwardOutgoing(new Translate(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation forwardIncomingScale(Interpolation interpolation, double startX, double endX,
        double startY, double endY)
    {
        return addForwardIncoming(new Scale(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation forwardOutgoingScale(Interpolation interpolation, double startX, double endX,
        double startY, double endY)
    {
        return addForwardOutgoing(new Scale(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation backwardIncomingScale(Interpolation interpolation, double startX, double endX,
        double startY, double endY)
    {
        return addBackwardIncoming(new Scale(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation backwardOutgoingScale(Interpolation interpolation, double startX, double endX,
        double startY, double endY)
    {
        return addBackwardOutgoing(new Scale(interpolation, startX, endX, startY, endY));
    }

    public JPageComponentTransitionAnimation forwardIncomingRotate(Interpolation interpolation, double centerX,
        double centerY, double startTheta, double endTheta)
    {
        return addForwardIncoming(new Rotate(interpolation, centerX, centerY, startTheta, endTheta));
    }

    public JPageComponentTransitionAnimation forwardOutgoingRotate(Interpolation interpolation, double centerX,
        double centerY, double startTheta, double endTheta)
    {
        return addForwardOutgoing(new Rotate(interpolation, centerX, centerY, startTheta, endTheta));
    }

    public JPageComponentTransitionAnimation backwardIncomingRotate(Interpolation interpolation, double centerX,
        double centerY, double startTheta, double endTheta)
    {
        return addBackwardIncoming(new Rotate(interpolation, centerX, centerY, startTheta, endTheta));
    }

    public JPageComponentTransitionAnimation backwardOutgoingRotate(Interpolation interpolation, double centerX,
        double centerY, double startTheta, double endTheta)
    {
        return addBackwardOutgoing(new Rotate(interpolation, centerX, centerY, startTheta, endTheta));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void drawForwardAnimation(Graphics2D g, int width, int height, Image outgoingImage, Image incomingImage,
        double fraction)
    {
        g.translate(width / 2, height / 2);

        Graphics2D gOutgoing = (Graphics2D) g.create();

        for (Transition transition : forwardOutgoingTransitions)
        {
            transition.apply(gOutgoing, width, height, fraction, true);
        }

        gOutgoing.drawImage(outgoingImage, -width / 2, -height / 2, null);

        Graphics2D gIncoming = (Graphics2D) g.create();

        for (Transition transition : forwardIncomingTransitions)
        {
            transition.apply(gIncoming, width, height, fraction, true);
        }

        gIncoming.drawImage(incomingImage, -width / 2, -height / 2, null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void drawBackwardAnimation(Graphics2D g, int width, int height, Image outgoingImage, Image incomingImage,
        double fraction)
    {
        g.translate(width / 2, height / 2);

        Graphics2D gOutgoing = (Graphics2D) g.create();

        for (Transition transition : backwardOutgoingTransitions)
        {
            transition.apply(gOutgoing, width, height, fraction, false);
        }

        gOutgoing.drawImage(outgoingImage, -width / 2, -height / 2, null);

        Graphics2D gIncoming = (Graphics2D) g.create();

        for (Transition transition : backwardIncomingTransitions)
        {
            transition.apply(gIncoming, width, height, fraction, false);
        }

        gIncoming.drawImage(incomingImage, -width / 2, -height / 2, null);
    }

}
